﻿using Mina.Core.Buffer;
using Mina.Core.Session;
using Mina.Filter.Codec;
using Mina.Filter.Codec.Demux;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using static SharpIM.HttpRequest;

namespace SharpIM.Http
{
    public class HttpRequestDecoder : IMessageDecoder
    {
        public const string httpContextAttributeName = "http.context";
        static string[] validMethods = new string[] { "GET", "POST" };
        public MessageDecoderResult Decodable(IoSession session, IoBuffer input)
        {
            HttpRequest req = null;
            var ctx = session.GetAttribute<HttpContext>(httpContextAttributeName);
            if (ctx == null)
            {
                ctx = new HttpContext();
                ctx.Request.IsSecure = (session.GetAttribute<bool?>("is_ssl_session") ?? false);
                ctx.Request.RemoteEndPoint = session.RemoteEndPoint as IPEndPoint;
                ctx.Request.LocalEndPoint = session.LocalEndPoint as IPEndPoint;
                session.SetAttribute(httpContextAttributeName, ctx);
            }
            req = ctx.Request;
            var offset = input.Position;
            try
            {
                if (req.Parser.State == HttpRequest.ParserState.HTTP_METHOD)
                {
                    string str;
                    if (IoBufferUtil.ReadUntil(ref offset, input, " ", HttpServer.maxHttpHeaderSize, HttpRequest.ParserError.HeadIsTooLong, out str) && str.Length > 0)
                    {
                        req.Parser.InputBufferPosition = offset;
                        req.Method = str;
                        if (!validMethods.Any(x => x == str))
                        {
                            return MessageDecoderResult.NotOK;
                        }
                        req.Parser.State = HttpRequest.ParserState.HTTP_RESOUCRE;
                        return Decodable(session, input);
                    }
                    else
                    {
                        return MessageDecoderResult.NotOK;
                    }
                }
                else if (req.Parser.State == HttpRequest.ParserState.HTTP_RESOUCRE)
                {
                    string str;
                    if (IoBufferUtil.ReadUntil(ref offset, input, " ", HttpServer.maxHttpHeaderSize, HttpRequest.ParserError.HeadIsTooLong, out str) && str.Length > 0)
                    {
                        req.Parser.InputBufferPosition = offset;
                        req.ResouceUrl = str;
                        req.Parser.State = HttpRequest.ParserState.HTTP_VERSION;
                        return Decodable(session, input);
                    }
                    else
                    {
                        return MessageDecoderResult.NotOK;
                    }
                }
                else if (req.Parser.State == HttpRequest.ParserState.HTTP_VERSION)
                {
                    string str;
                    if (IoBufferUtil.ReadUntil(ref offset, input, "\r\n", HttpServer.maxHttpHeaderSize, HttpRequest.ParserError.HeadIsTooLong, out str) && str.Length > 0)
                    {
                        req.Parser.InputBufferPosition = offset;
                        req.Version = str;
                        req.Parser.State = HttpRequest.ParserState.HTTP_ENTITY;
                        return Decodable(session, input);
                    }
                    else
                    {
                        return MessageDecoderResult.NotOK;
                    }
                }
                else if (req.Parser.State == HttpRequest.ParserState.HTTP_ENTITY)
                {
                    string str;
                    if (IoBufferUtil.ReadUntil(ref offset, input, "\r\n\r\n", HttpServer.maxHttpHeaderSize, HttpRequest.ParserError.HeadIsTooLong, out str) && str.Length > 0)
                    {
                        req.Parser.InputBufferPosition = offset;
                        req.RawHeader = str;
                        req.ParseHeaders();
                        req.Parser.State = HttpRequest.ParserState.HTTP_BODY;
                        return MessageDecoderResult.OK;
                    }
                    else
                    {
                        return MessageDecoderResult.NotOK;
                    }
                }
                else
                {
                    return MessageDecoderResult.NotOK;
                }
            }
            catch (ParserReachSizeLimitException ex)
            {
                req.Parser.State = ParserState.ERROR;
                req.Parser.Error = ex.Error;
                return MessageDecoderResult.OK;
            }
        }

        public MessageDecoderResult Decode(IoSession session, IoBuffer input, IProtocolDecoderOutput output)
        {
            var ctx = session.GetAttribute<HttpContext>(httpContextAttributeName);
            var req = ctx.Request;
            input.Position = req.Parser.InputBufferPosition;
            if (req.Parser.State == HttpRequest.ParserState.HTTP_BODY)
            {
                if (req.ContentLength > 0)
                {
                    if (req.ContentLength > HttpServer.maxHttpBodySize)
                    {
                        req.Parser.Error = HttpRequest.ParserError.BodyIsTooLong;
                        req.Parser.State = HttpRequest.ParserState.ERROR;
                    }
                    if (input.Remaining < req.ContentLength)
                    {
                        return MessageDecoderResult.NeedData;
                    }
                    if (req.ContentType.StartsWith("application/json"))
                    {
                        var cnt = new byte[req.ContentLength];
                        input.Get(cnt, 0, cnt.Length);
                        req.Payload = new JsonPayload() { Value = Encoding.UTF8.GetString(cnt) };
                    }
                    else if (req.ContentType.StartsWith("application/x-www-form-urlencoded"))
                    {
                        var cnt = new byte[req.ContentLength];
                        input.Get(cnt, 0, cnt.Length);
                        req.Payload = new FormPayload() { Value = Encoding.UTF8.GetString(cnt) };
                    }
                    else if (req.ContentType.StartsWith("multipart/form-data"))
                    {
                        var cnt = new byte[req.ContentLength];
                        input.Get(cnt, 0, cnt.Length);
                        req.Payload = new MultipartFormPayload() { Value = cnt };
                    }

                    req.Parser.State = HttpRequest.ParserState.END;
                    output.Write(req);
                    return MessageDecoderResult.OK;
                }
                else
                {
                    output.Write(req);
                    return MessageDecoderResult.OK;
                }
            }
            else if (req.Parser.State == ParserState.ERROR)
            {
                output.Write(req);
                //consume all rest data
                input.Position = input.Position += input.Remaining;
                return MessageDecoderResult.OK;
            }
            else
            {
                return MessageDecoderResult.NotOK;
            }
        }

        public void FinishDecode(IoSession session, IProtocolDecoderOutput output)
        {

        }
    }
}
